{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "83fcadf3",
   "metadata": {},
   "source": [
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/chatbot-summarization.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239436-lesson-5-chatbot-w-summarizing-messages-and-memory)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b651ead9-5504-45ee-938d-f91ac78dddd1",
   "metadata": {},
   "source": [
    "# Chatbot with message summarization\n",
    "\n",
    "## Review\n",
    "\n",
    "We've covered how to customize graph state schema and reducer. \n",
    " \n",
    "We've also shown a number of ways to trim or filter messages in graph state. \n",
    "\n",
    "## Goals\n",
    "\n",
    "Now, let's take it one step further! \n",
    "\n",
    "Rather than just trimming or filtering messages, we'll show how to use LLMs to produce a running summary of the conversation.\n",
    " \n",
    "This allows us to retain a compressed representation of the full conversation, rather than just removing it with trimming or filtering.\n",
    "\n",
    "We'll incorporate this summarization into a simple Chatbot.  \n",
    "\n",
    "And we'll equip that Chatbot with memory, supporting long-running conversations without incurring high token cost / latency. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "000a6daa-92ad-4e57-a060-d1c81176eb0d",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --quiet -U langchain_core langgraph langchain_openai"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "09201a62",
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "OPENAI_API_KEY:  ········\n"
     ]
    }
   ],
   "source": [
    "import os, getpass\n",
    "\n",
    "def _set_env(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"{var}: \")\n",
    "\n",
    "_set_env(\"OPENAI_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfddfce9-3a9b-4b35-a76d-28265515aabd",
   "metadata": {},
   "source": [
    "We'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "464856d4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "LANGCHAIN_API_KEY:  ········\n"
     ]
    }
   ],
   "source": [
    "_set_env(\"LANGCHAIN_API_KEY\")\n",
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "537ade30-6a0e-4b6b-8bcd-ce90790b6392",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "model = ChatOpenAI(model=\"gpt-4o\",temperature=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db3afac3-8b7a-45db-a3c1-7e4125c1bc8b",
   "metadata": {},
   "source": [
    "We'll use `MessagesState`, as before.\n",
    "\n",
    "In addition to the built-in `messages` key, we'll now include a custom key (`summary`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "948e60f0-5c76-4235-b40e-cf523205d40e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import MessagesState\n",
    "class State(MessagesState):\n",
    "    summary: str"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6855ea31-5cc1-4277-a189-0b72459f67ec",
   "metadata": {},
   "source": [
    "We'll define a node to call our LLM that incorporates a summary, if it exists, into the prompt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c3f7d19b-afe0-4381-9b1a-0a832b162e7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n",
    "\n",
    "# Define the logic to call the model\n",
    "def call_model(state: State):\n",
    "    \n",
    "    # Get summary if it exists\n",
    "    summary = state.get(\"summary\", \"\")\n",
    "\n",
    "    # If there is summary, then we add it\n",
    "    if summary:\n",
    "        \n",
    "        # Add summary to system message\n",
    "        system_message = f\"Summary of conversation earlier: {summary}\"\n",
    "\n",
    "        # Append summary to any newer messages\n",
    "        messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n",
    "    \n",
    "    else:\n",
    "        messages = state[\"messages\"]\n",
    "    \n",
    "    response = model.invoke(messages)\n",
    "    return {\"messages\": response}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6882042c-b42d-4d52-a6a7-6ec8efa72450",
   "metadata": {},
   "source": [
    "We'll define a node to produce a summary.\n",
    "\n",
    "Note, here we'll use `RemoveMessage` to filter our state after we've produced the summary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "78c7aa59-3760-4e76-93f1-bc713e3ec39e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def summarize_conversation(state: State):\n",
    "    \n",
    "    # First, we get any existing summary\n",
    "    summary = state.get(\"summary\", \"\")\n",
    "\n",
    "    # Create our summarization prompt \n",
    "    if summary:\n",
    "        \n",
    "        # A summary already exists\n",
    "        summary_message = (\n",
    "            f\"This is summary of the conversation to date: {summary}\\n\\n\"\n",
    "            \"Extend the summary by taking into account the new messages above:\"\n",
    "        )\n",
    "        \n",
    "    else:\n",
    "        summary_message = \"Create a summary of the conversation above:\"\n",
    "\n",
    "    # Add prompt to our history\n",
    "    messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n",
    "    response = model.invoke(messages)\n",
    "    \n",
    "    # Delete all but the 2 most recent messages\n",
    "    delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n",
    "    return {\"summary\": response.content, \"messages\": delete_messages}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f982993e-f4be-4ff7-9a38-886f75398b3d",
   "metadata": {},
   "source": [
    "We'll add a conditional edge to determine whether to produce a summary based on the conversation length."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "b507665d-7f5d-442a-b498-218c94c5dd8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import END\n",
    "# Determine whether to end or summarize the conversation\n",
    "def should_continue(state: State):\n",
    "    \n",
    "    \"\"\"Return the next node to execute.\"\"\"\n",
    "    \n",
    "    messages = state[\"messages\"]\n",
    "    \n",
    "    # If there are more than six messages, then we summarize the conversation\n",
    "    if len(messages) > 6:\n",
    "        return \"summarize_conversation\"\n",
    "    \n",
    "    # Otherwise we can just end\n",
    "    return END"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a838f4c-7067-4f7f-a4c4-6654e11214cd",
   "metadata": {},
   "source": [
    "## Adding memory\n",
    "\n",
    "Recall that [state is transient](https://github.com/langchain-ai/langgraph/discussions/352#discussioncomment-9291220) to a single graph execution.\n",
    "\n",
    "This limits our ability to have multi-turn conversations with interruptions. \n",
    "\n",
    "As introduced at the end of Module 1, we can use [persistence](https://langchain-ai.github.io/langgraph/how-tos/persistence/) to address this! \n",
    " \n",
    "LangGraph can use a checkpointer to automatically save the graph state after each step.\n",
    "\n",
    "This built-in persistence layer gives us memory, allowing LangGraph to pick up from the last state update. \n",
    "\n",
    "As we previously showed, one of the easiest to work with is `MemorySaver`, an in-memory key-value store for Graph state.\n",
    "\n",
    "All we need to do is compile the graph with a checkpointer, and our graph has memory!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "1d57516d-f9f1-4d3c-a84a-7277b5ce6df6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import StateGraph, START\n",
    "\n",
    "# Define a new graph\n",
    "workflow = StateGraph(State)\n",
    "workflow.add_node(\"conversation\", call_model)\n",
    "workflow.add_node(summarize_conversation)\n",
    "\n",
    "# Set the entrypoint as conversation\n",
    "workflow.add_edge(START, \"conversation\")\n",
    "workflow.add_conditional_edges(\"conversation\", should_continue)\n",
    "workflow.add_edge(\"summarize_conversation\", END)\n",
    "\n",
    "# Compile\n",
    "memory = MemorySaver()\n",
    "graph = workflow.compile(checkpointer=memory)\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "d0bd5d23-ac3b-4496-a049-9a9f97d2feb9",
   "metadata": {},
   "source": [
    "## Threads\n",
    "\n",
    "The checkpointer saves the state at each step as a checkpoint.\n",
    "\n",
    "These saved checkpoints can be grouped into a `thread` of conversation.\n",
    "\n",
    "Think about Slack as an analog: different channels carry different conversations.\n",
    "\n",
    "Threads are like Slack channels, capturing grouped collections of state (e.g., conversation).\n",
    "\n",
    "Below, we use `configurable` to set a thread ID.\n",
    "\n",
    "![state.jpg](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbadf3b379c2ee621adfd1_chatbot-summarization1.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "2566c93b-13e6-4a53-bc0f-b00fff691d30",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Hello Lance! How can I assist you today?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "You mentioned that your name is Lance. How can I help you today?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "That's great! The San Francisco 49ers have a rich history and a passionate fan base. Do you have a favorite player or a memorable game that you enjoyed?\n"
     ]
    }
   ],
   "source": [
    "# Create a thread\n",
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "\n",
    "# Start conversation\n",
    "input_message = HumanMessage(content=\"hi! I'm Lance\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config) \n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()\n",
    "\n",
    "input_message = HumanMessage(content=\"what's my name?\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config) \n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()\n",
    "\n",
    "input_message = HumanMessage(content=\"i like the 49ers!\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config) \n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "531e5b63-5e8b-486e-baa0-a45521e2fbc2",
   "metadata": {},
   "source": [
    "Now, we don't yet have a summary of the state because we still have < = 6 messages.\n",
    "\n",
    "This was set in `should_continue`. \n",
    "\n",
    "```\n",
    "    # If there are more than six messages, then we summarize the conversation\n",
    "    if len(messages) > 6:\n",
    "        return \"summarize_conversation\"\n",
    "```\n",
    "\n",
    "We can pick up the conversation because we have the thread."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "91b82aaa-17f9-49e2-9528-f4b22e23ebcb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "''"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.get_state(config).values.get(\"summary\",\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "068a93e9-f716-4980-8edf-94115017d865",
   "metadata": {},
   "source": [
    "The `config` with thread ID allows us to proceed from the previously logged state!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "24b34f0f-62ef-4008-8e96-480cbe92ea3e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Yes, as of September 2023, Nick Bosa became the highest-paid defensive player in NFL history. He signed a five-year contract extension with the San Francisco 49ers worth $170 million, with $122.5 million guaranteed. Bosa is known for his exceptional skills as a defensive end and has been a key player for the 49ers.\n"
     ]
    }
   ],
   "source": [
    "input_message = HumanMessage(content=\"i like Nick Bosa, isn't he the highest paid defensive player?\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config) \n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "22f1b35f-e4bb-47f6-87b1-d84d8aed9aa9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Lance introduced himself and expressed his support for the San Francisco 49ers, mentioning a particular liking for Nick Bosa. The conversation highlighted that Nick Bosa became the highest-paid defensive player in NFL history as of September 2023, with a significant contract extension with the 49ers.'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.get_state(config).values.get(\"summary\",\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad7cc0ab-905a-4037-b7cb-69db5b89591e",
   "metadata": {},
   "source": [
    "## LangSmith\n",
    "\n",
    "Let's review the trace!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "88e22d1f-943b-4a3c-8f06-87e7be4ca198",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Created a running summary of our conversation with checkpointer\n",
    "#So when our convo reaches six total messages, we call the condtiional edge to summarize\n",
    "#Then we generate or add to our summary and remove all but the latest two messages from our conversation\n",
    "#Then next time we ask the ai model, instead of invoking on all messages, which would be a lot of input tokens\n",
    "#We can just invoke on latest two messages and the summary, which is much less"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
